using System;
using System.Configuration;
using System.Globalization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.ServiceModel.Samples.UdpTransport;

namespace Lab620.WcfRadiusService.Extentions
{
    /// <summary>
    /// Collection of constants used by the Udp Channel classes
    /// </summary>
    static class RadiusTransportConstants
    {
        internal const string EventLogSourceName = "RadiusServiceConsoleHost.Extentions";
        internal const string Scheme = "radius.udp";
        internal const string RadiusBindingSectionName = "system.serviceModel/bindings/radiusBinding";
        internal const string RadiusTransportSectionName = "radiusTransport";
        internal const int WSAETIMEDOUT = 10060;

        static readonly MessageEncoderFactory messageEncoderFactory;
        static RadiusTransportConstants()
        {
            messageEncoderFactory = new RadiusMessageEncodingBindingElement().CreateMessageEncoderFactory();
        }

        // ensure our advertised MessageVersion matches the version we're
        // using to serialize/deserialize data to/from the wire
        internal static MessageVersion MessageVersion
        {
            get
            {
                return messageEncoderFactory.MessageVersion;
            }
        }

        // we can use the same encoder for all our Udp Channels as it's free-threaded
        internal static MessageEncoderFactory DefaultMessageEncoderFactory
        {
            get
            {
                return messageEncoderFactory;
            }
        }
    }

    static class RadiusConfigurationStrings
    {
        public const string ProfileSource = "profileSource";
        public const string MembershipProvider = "membershipProvider";
        public const string SharedSecret = "sharedSecret";
    }

    /// <summary>
    /// Binding for Radius.  
    /// </summary>
    public class RadiusBinding : Binding
    {
        private MessageEncodingBindingElement encoding;
        private UdpTransportBindingElement transport;
        private RadiusValidationBehavior behaviour;

        public RadiusBinding()
        {
            Initialize();
        }

        public RadiusBinding(string configurationName)
            : this()
        {
            ApplyConfiguration(configurationName);
        }

        public override string Scheme
        {
            get { return "radius.udp"; }
        }

        public EnvelopeVersion SoapVersion
        {
            get { return EnvelopeVersion.Soap11; }
        }

        public string SharedSecret { get; set; }

        public object ProfileSource { get; set; }

        /// <summary>
        /// Create the set of binding elements that make up this binding. 
        /// </summary>
        /// <returns></returns>
        public override BindingElementCollection CreateBindingElements()
        {
            var bindingElements = new BindingElementCollection { encoding, transport };
            return bindingElements.Clone();
        }

        private void ApplyConfiguration(string configurationName)
        {
            var section =
                (RadiusBindingCollectionElement)
                ConfigurationManager.GetSection(RadiusTransportConstants.RadiusBindingSectionName);
            RadiusBindingConfigurationElement element = section.Bindings[configurationName];
            if (element == null)
            {
                throw new ConfigurationErrorsException(string.Format(CultureInfo.CurrentCulture,
                                                                     "There is no binding named {0} at {1}.",
                                                                     configurationName, section.BindingName));
            }
            element.ApplyConfiguration(this);
        }

        private void Initialize()
        {
            transport = new UdpTransportBindingElement();
            encoding = new RadiusMessageEncodingBindingElement();
        }

        private void InitializeFrom(TransportBindingElement udpTransportBindingElement)
        {
            transport.Multicast = false;
            transport.MaxBufferPoolSize = udpTransportBindingElement.MaxBufferPoolSize;
            transport.MaxReceivedMessageSize = udpTransportBindingElement.MaxReceivedMessageSize;

            //((TextMessageEncodingBindingElement)encoding).WriteEncoding =
            //    messageEncodingBindingElement.WriteEncoding;
            //messageEncodingBindingElement.ReaderQuotas.CopyTo(
            //    ((TextMessageEncodingBindingElement)encoding).ReaderQuotas);
        }

        //try to create a RadiusBinding from the collection of BindingElement
        //returns true if it is possible, with the resulting binding.
        public static bool TryCreate(BindingElementCollection elements, out Binding binding)
        {
            binding = null;
            if (elements.Count > 4)
            {
                return false;
            }

            RadiusMessageEncodingBindingElement radiusMessageEncodingBindingElement = null;
            UdpTransportBindingElement udpTransportBindingElement = null;

            foreach (BindingElement element in elements)
            {
                if (element is TransportBindingElement)
                {
                    udpTransportBindingElement = element as UdpTransportBindingElement;
                }
                else if (element is RadiusMessageEncodingBindingElement)
                {
                    radiusMessageEncodingBindingElement = element as RadiusMessageEncodingBindingElement;
                }
                else
                {
                    return false;
                }
            }

            if (udpTransportBindingElement == null)
            {
                return false;
            }
            if (radiusMessageEncodingBindingElement == null)
            {
                return false;
            }

            var radiusBinding = new RadiusBinding();
            radiusBinding.InitializeFrom(udpTransportBindingElement);
            if (!radiusBinding.IsBindingElementsMatch(udpTransportBindingElement,
                                                                radiusMessageEncodingBindingElement))
            {
                return false;
            }

            binding = radiusBinding;

            return true;
        }

        private bool IsBindingElementsMatch(BindingElement udpTransportBindingElement,
                                            BindingElement textMessageEncodingBindingElement)
        {
            if (!IsTransportMatch(transport, udpTransportBindingElement))
            {
                return false;
            }

            if (!IsEncodingMatch(encoding, textMessageEncodingBindingElement))
            {
                return false;
            }

            return true;
        }

        private static bool IsTransportMatch(BindingElement a, BindingElement b)
        {
            if (b == null)
            {
                return false;
            }

            var transportA = a as UdpTransportBindingElement;
            var transportB = b as UdpTransportBindingElement;

            if (transportB == null)
            {
                return false;
            }

            if (transportA == null)
            {
                return false;
            }

            if (transportA.MaxBufferPoolSize != transportB.MaxBufferPoolSize)
            {
                return false;
            }
            if (transportA.MaxReceivedMessageSize != transportB.MaxReceivedMessageSize)
            {
                return false;
            }
            if (transportA.Multicast != transportB.Multicast)
            {
                return false;
            }

            return true;
        }

        private static bool IsEncodingMatch(BindingElement a, BindingElement b)
        {
            if (b == null)
            {
                return false;
            }

            var messageEncodingBindingElement = b as MessageEncodingBindingElement;
            if (messageEncodingBindingElement == null)
            {
                return false;
            }

            var textA = a as RadiusMessageEncodingBindingElement;
            var textB = b as RadiusMessageEncodingBindingElement;
            if (textB == null)
            {
                return false;
            }

            if (textA == null)
            {
                return false;
            }
            //if (textA.MaxReadPoolSize != textB.MaxReadPoolSize)
            //{
            //    return false;
            //}
            //if (textA.MaxWritePoolSize != textB.MaxWritePoolSize)
            //{
            //    return false;
            //}

            //// compare XmlDictionaryReaderQuotas
            //if (textA.ReaderQuotas.MaxStringContentLength != textB.ReaderQuotas.MaxStringContentLength)
            //{
            //    return false;
            //}
            //if (textA.ReaderQuotas.MaxArrayLength != textB.ReaderQuotas.MaxArrayLength)
            //{
            //    return false;
            //}
            //if (textA.ReaderQuotas.MaxBytesPerRead != textB.ReaderQuotas.MaxBytesPerRead)
            //{
            //    return false;
            //}
            //if (textA.ReaderQuotas.MaxDepth != textB.ReaderQuotas.MaxDepth)
            //{
            //    return false;
            //}
            //if (textA.ReaderQuotas.MaxNameTableCharCount != textB.ReaderQuotas.MaxNameTableCharCount)
            //{
            //    return false;
            //}

            //if (textA.WriteEncoding.EncodingName != textB.WriteEncoding.EncodingName)
            //{
            //    return false;
            //}
            if (!IsMessageVersionMatch(textA.MessageVersion, textB.MessageVersion))
            {
                return false;
            }

            return true;
        }

        private static bool IsMessageVersionMatch(MessageVersion a, MessageVersion b)
        {
            if (b == null)
            {
                throw new ArgumentNullException("b");
            }
            if (a.Addressing == null)
            {
                throw new InvalidOperationException("MessageVersion.Addressing cannot be null");
            }

            if (a.Envelope != b.Envelope)
            {
                return false;
            }
            //if (a.Addressing.Namespace != b.Addressing.Namespace)
            //{
            //    return false;
            //}

            return true;
        }

    }
}